import { arrayMethods } from "./array"
import Dep from "./dep"

// 观察者类，会被附加到每个被观察的对象上，value.__ob__ = this
export class Observer {
  constructor (value) {

    // 给每个对象添加收集功能
    this.dep = new Dep() // 所有对象都增加 dep
    
    // Object.defineProperty 只能劫持已经存在的属性
    // Vue对此单独写了一些api，如：$set $delete等

    // 给数据添加__ob__属性，如果数据上有__ob__则说明这个属性被观测过了
    Object.defineProperty(value, '__ob__', {
      value: this,
      enumerable: false, // 不可枚举
      configurable: true,
      writable: true
    })
    // 处理数组
    if (Array.isArray(value)) {
      protoArgument(value, arrayMethods)
      this.observeArray(value)
    } else {
      // 处理对象
      this.walk(value)
    }
  }

  walk (obj) {
    // "重新定义"属性   性能差
    for (const key in obj) {
      defineReactive(obj, key, obj[key])
    }
  }

  // 遍历数组，为数组的每一项设置观察，处理数组元素为对象的情况
  observeArray (items) {
    for (let i = 0, len = items.length; i < len; i++) {
      observe(items[i])
    }
  }
}

export function observe (value) {
  // 非对象不做响应式处理
  if (typeof value !== 'object' || value === null) return

  // 如果存在 __ob__ 属性, 说明已经经过响应式处理了
  if (value.__ob__ instanceof Observer) return value.__ob__

  // 如果一个对象被劫持过了，就不需要再被劫持
  // 要判断一个对象是否被劫持过，可以添加一个实例，用实例来判断是否被劫持过
  return new Observer(value)
}

// 深层次嵌套递归，递归多性能差，不存在属性监控不到，存在的属性要重写方法：vue3 => proxy
function dependArray (value) {
  for (let i = 0; i < value.length; i++) {
    const current = value[i]
    current.__ob__ && current.__ob__.dep.depend()

    if (Array.isArray(current)) {
      dependArray(current)
    }
  }
}

export function defineReactive (obj, key, val) {
  const childOb = observe(val) // childOb.dep 用来收集依赖
  const dep = new Dep()

  Object.defineProperty(obj, key, {
    get () {
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend() // 对象和数组本身也实现依赖收集
          if (Array.isArray(val)) {
            dependArray(val)
          }
        }
      }
      return val
    },
    set (newVal) {
      if (newVal === val) return

      val = newVal

      dep.notify()
    }
  })
}

function protoArgument (target, src) {
  target.__proto__ = src
}